#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/param.h>

#include "../includes/functions.h"
#include "../includes/consts.h"
#include "../includes/queue.h"
#include "../includes/erproc.h"
#include "../includes/db.h"
#include "../includes/user_socket_link.h"


/*                                                                     
   dd99 99                                                                                                      
  d9"   99                                                                                 
  99    99                                                           
NN99NNN 99 ,ddEE22ed, 99,dEDed,,ddE2ed,   
  99    99 ""     `D9 99E'   "99"    "9d 
  99    99 ,ddEEEEE99 99      99      99  
  99    99 99,    ,99 99      99      99 
  99    99 `"9eedE"29 99      99      99            

            .-.
           ((`-)
            \\
             \\
      .="""=._))
     /  .,   .'
    /__(,_.-'
   `    /|
       /_|__
         | `))
         |
        -"==

*/


char HELP_MESS[MAX_SIZE] = "SYNOPSIS\n"
                        "\tserver [PASSWORD] [OPTION]\n\n"
                        "OPTION\n"
                        "\t-p [PASSWORD]\n"
                        "\t\tPassword is required to log in the admin\n\n"
                        "\t-h\n"
                        "\t\tPrint help message\n\n"
                        "\t-l [PATH]\n"
                        "\t\tLogs will be stored in the file along this path\n\n"
                        "\t-g [PATH]\n"
                        "\t\tPath to the working directory of the group\n"
                        "PORTS\n"
                        "\t\%s for admin\n"
                        "\t\%s for client\n\n";


int main(int argc, char *argv[]){
    fd_set master;
    fd_set read_fds;
    int fdmax = 0;
    int is_log = 0;
    int admin_is_con = 0;
    FILE *log_file = NULL;
    queue_t queue;
    char pass[MAX_SIZE] = "\0";
    char tmp[MAX_SIZE]; 
    char path_log[MAX_SIZE];
    char path_work_dir[MAX_SIZE] = "\0";
    sqlite3 *DB; 
    FILE *QUEUE_FILE;
    user_socket_link_t user_soc_link[MAX_SIZE];

    char user_name[MAX_SIZE];
    char user_pass[MAX_SIZE];

    int listener;
    int admin = -1, admin_list;
    int newfd;
    struct sockaddr_in remoteaddr;
    socklen_t addrlen;
    int auth_users[MAX_SIZE];

    /* sqlite3 *db; */

    char buf[MAX_SIZE];
    int nbytes;

    char remoteIP[INET6_ADDRSTRLEN];

    struct addrinfo hints, *ai, *p;

    struct stat st = {0};


    if (argc == 1){
        printf(HELP_MESS, PORT_ADMIN, PORT);
        exit(EXIT_SUCCESS);
    }

    for (int i = 1; i < argc; ++i){
        if (Strcmp(argv[i], "-h")){
            printf(HELP_MESS, PORT_ADMIN, PORT);                
            exit(EXIT_SUCCESS);
        }
        else if (Strcmp(argv[i], "-l") && (i+1 <= argc-1)){
            strncpy(path_log, argv[i+1], MAX_SIZE);
            log_file = fopen(path_log, "a");
            is_log = 1;
            i++;
        }
        else if (Strcmp(argv[i], "-p") && (i+1 <= argc-1)){
            strncpy(pass, argv[i+1], MAX_SIZE);
            i++;
        }
        else if (Strcmp(argv[i], "-g") && (i+1 <= argc-1)){
            strncpy(path_work_dir, argv[i+1], MAX_SIZE);
            if (stat(path_work_dir, &st) == -1)
                mkdir(path_work_dir, 0700); 

            OpenDB(path_work_dir, QUEUE_FILE, DB, is_log, log_file);
            i++;
        }
        else{
            printf("[Incorrect option!]\n");
            exit(EXIT_FAILURE);
        }
    }

    if (Strcmp(pass, "")){
        fprintf(stderr, "[Password wasn't entered!]\n");
        exit(EXIT_FAILURE);
    }
    if (Strcmp(path_work_dir, "")){
        fprintf(stderr, "[Path to work directory wasn't entered!]\n");
        exit(EXIT_FAILURE);
    }
   
    init_queue(&queue);

    FD_ZERO(&master);
    FD_ZERO(&read_fds);

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    Getaddrinfo(NULL, PORT, &hints, &ai);
    listener = Socket(ai, bind);

    Getaddrinfo(NULL, PORT_ADMIN, &hints, &ai);
    admin_list = Socket(ai, bind);

    Listen(listener, 50);
    Listen(admin_list, 1);

    FD_SET(listener, &master);
    FD_SET(admin_list, &master);

    fdmax = admin_list;

    freeaddrinfo(ai);

    ServerPrint(is_log, log_file, "=====================================\nSTARTSERVER\n");

    for (;;){
        read_fds = master;

        Select(fdmax+1, &read_fds);

        for (int fd = 0; fd <= fdmax; ++fd){
            if (FD_ISSET(fd, &read_fds)){
                //Новый пользователь хочет подключиться
                if (fd == listener){
                    addrlen = sizeof remoteaddr;
                    newfd = Accept(listener,
                                   (struct sockaddr*)&remoteaddr, &addrlen);
                
                    FD_SET(newfd, &master);
                    
                    fdmax = MAX(newfd, fdmax);

                    ServerPrint(is_log, log_file, "SELECT SERVER: NEW CONNECTION FROM %s on "
                                "SOCKET %d\n",
                                inet_ntop(remoteaddr.sin_family,
                                &remoteaddr.sin_addr,
                                remoteIP, INET6_ADDRSTRLEN),
                                newfd);
                    
                    strncpy(queue[fd].ip_addr, 
                            inet_ntop(remoteaddr.sin_family, &remoteaddr.sin_addr,remoteIP, INET6_ADDRSTRLEN),
                            MAX_SIZE);
                    
                }
                //Новый админ хочет подключиться
                else if (fd == admin_list){
                    if (!admin_is_con){
                        addrlen = sizeof remoteaddr;
                        admin = Accept(admin_list, 
                                       (struct sockaddr*)&remoteaddr, 
                                       &addrlen);
                        
                        admin_is_con = 1;

                        FD_SET(admin, &master);

                        if ((nbytes = recv(admin, buf, MAX_SIZE, 0)) <= 0){
                            if (nbytes == 0){

                                ServerPrint(is_log, log_file, "SELECTSERVER: ADMIN HUNG UP\n");
                            }
                            else
                                perror("recv");

                            Close(admin);
                            FD_CLR(admin, &master);
                        }
                        else{
                            if (Strcmp(buf, pass)){
                                Send(admin, OK, MAX_SIZE);

                                ServerPrint(is_log, log_file, 
                                       "SELECT SERVER: NEW ADMIN FROM %s on "
                                       "SOCKET %d\n",   
                                       inet_ntop(remoteaddr.sin_family,
                                       &remoteaddr.sin_addr,
                                       remoteIP, INET6_ADDRSTRLEN),
                                       admin);

                                fdmax = MAX(admin, fdmax);
                            }
                            else{
                                Send(admin, FAILED, MAX_SIZE);
                                Close(admin);
                                FD_CLR(admin, &master);
                                admin_is_con = 0;

                                ServerPrint(is_log, log_file, 
                                       "UNSUCCESSFUL ATTEMPT TO LOG IN FOR THE ADMIN: "
                                       "%s on socket %d\n",
                                       inet_ntop(remoteaddr.sin_family,
                                       &remoteaddr.sin_addr,
                                       remoteIP, INET6_ADDRSTRLEN),
                                       admin);
                            }
                        }
                    }
                }
                //Админ прислал данные
                else if (fd == admin){
                    if ((nbytes = recv(admin, buf, sizeof buf, 0)) <= 0){
                        if (nbytes == 0){
                            ServerPrint(is_log, log_file, "SELECTSERVER: ADMIN HUNG UP\n");
                        }
                        else
                            perror("recv");
                        
                        admin_is_con = 0;
                        FD_CLR(admin, &master);
                        Close(admin);
                    }
                
                    else{
                        if (Strcmp(buf, "del")){
                            if (!is_empty_queue(&queue)){
                                remove_queue(&queue, queue.queue[0].fd);
                                Send(admin, OK, MAX_SIZE);
                                SendQueue(&queue, admin);
                                ServerPrint(is_log, log_file, 
                                            "ADMIN REMOVE USER ON SOCKET %d FROM THE QUEUE\n", 
                                            queue.queue[0].fd);
                            }
                            else{
                                Send(admin, FAILED, MAX_SIZE);
                                ServerPrint(is_log, log_file, "ADMIN COULD NOT REMOVE USER\n");
                            }
                        }
                        else if (Strcmp(buf, "del all")){
                            if (!is_empty_queue(&queue)){
                                int tmp_len = queue.len;
                                for (int j = 0; j < tmp_len; ++j){
                                    remove_queue(&queue, queue.queue[j].fd);
                                }
                                Send(admin, OK, MAX_SIZE);
                                ServerPrint(is_log, log_file, "ADMIN CLEARED THE QUEUE\n");
                            }
                            else{
                                Send(admin, FAILED, MAX_SIZE);
                                ServerPrint(is_log, log_file, "ADMIN COULD NOT CLEAR QUEUE\n");
                            }
                        }
                        else if (Strcmp(buf, "list")){
                            SendQueue(&queue, admin);
                            ServerPrint(is_log, log_file, "ADMIN REQUESTED THE QUEUE\n");
                        }
                        else{
                            Send(admin, COM_NOT_REC, MAX_SIZE);
                            ServerPrint(is_log, log_file, "ADMIN SENT UNRECOGNIZABLE COMMAND\n");
                        }
                    }
                }
                //Пришли данные от пользователя 
                else{
                    //Пользователь отключился
                    if ((nbytes = recv(fd, buf, sizeof buf, 0)) <= 0){
                        if (nbytes == 0)
                            ServerPrint(is_log, log_file, "SELECTSERVER: SOCKET %d hung up\n", fd);
                        else
                            perror("recv");

                        Close(fd);
                        FD_CLR(fd, &master);
                        queue.fd[fd] = 0;
                    }
                    //Иначе пользователь отправил данные
                    else{
                        int id = -1;
                        
                        if (Strcmp(buf, "RR")){
                            ParseDataUser(fd, user_name, user_pass);
                            if (id = RegUserInDB(fd, user_name, user_pass, DB, is_log, log_file)){
                                Send(fd, OK, MAX_SIZE);
                                queue[fd].DBid = id;
                                strncpy(queue[fd].user_name, user_name, MAX_SIZE);
                            }
                            else
                                Send(fd, FAILED, MAX_SIZE);
                        }
                        else if (Strcmp(buf, "ER")){
                            ParseDataUser(fd, user_name, user_pass);
                            if (id = CheckUserInDB(fd, user_name, user_pass, DB, is_log, log_file)){
                                Send(fd, OK, MAX_SIZE);
                                queueu[fd].DBid = id;
                                strncpy(queue[fd].user_name, user_name, MAX_SIZE);
                            }
                            else
                                Send(fd, FAILED, MAX_SIZE);
                        }
                        else if (user_socket_link[fd].id == 0){
                            ServerPrint(is_log, log_file, 
                                        "Attempt to send data "
                                        "to an non-registered user on socket %d\n", fd);
                            Close(fd);
                            continue;
                        }
                        else if (Strcmp(buf, "list")){
                            SendQueue(&queue, fd);
                            ServerPrint(is_log, log_file, "USER ON SOCKET %d REQUEST QUEUE\n", fd);
                        }
                        else if (strstr(buf, "mes") - buf == 0){
                            if (is_in_queue(&queue, fd)){
                                Send(fd, FAILED, MAX_SIZE);
                                ServerPrint(is_log, log_file, 
                                            "SERVER REFUSED TO ACCEPT USER DATA ON SOCKET %d\n", fd);
                            }
                            else{
                                Send(fd, OK, MAX_SIZE);
                                insert_queue(&queue, buf+5, fd);
                                ServerPrint(is_log, log_file, 
                                            "SERVER ACCEPTED THE USER'S DATA ON SOCKET %d: \"%s\"\n", 
                                            fd, buf+5);
                            }
                        }
                    }
                }
            }
        }
    }
}
